/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define LOG_TAG "AMessage"
//#define LOG_NDEBUG 0
//#define DUMP_STATS

#include <ctype.h>
#include <cstring>
#include <cassert>
#include <utility>
#include "AMessage.h"
#include "ALooperRoster.h"
#include "AHandler.h"
#include "string"
extern ALooperRoster gLooperRoster;

int32_t AReplyToken::setReply(const std::shared_ptr<AMessage> &reply) {
    if (mReplied) {
        printf("trying to post a duplicate reply\n");
        return -EBUSY;
    }

    assert(mReply == nullptr);

    mReply = reply;
    mReplied = true;
    return 0;
}

AMessage::AMessage(void)
        : mWhat(0),
          mTarget(0) {
}

AMessage::AMessage(uint32_t what, const std::shared_ptr<AHandler> &handler)
        : mWhat(what) {

    setTarget(handler);
}

AMessage::~AMessage() {
    clear();
}

void AMessage::setWhat(uint32_t what) {
    mWhat = what;
}

uint32_t AMessage::what() const {
    return mWhat;
}

void AMessage::setTarget(const std::shared_ptr<AHandler> &handler) {
    if (handler == nullptr) {
        mTarget = 0;
        mHandler.reset();
        mLooper.reset();
    } else {
        mTarget = handler->id();
        mHandler = handler;
        mLooper = handler->getLooper();
    }
}

void AMessage::clear() {
    // Item needs to be handled delicately
    for (Item &item : mItems) {
        freeItemValue(&item);
    }
    mItems.clear();
}

void AMessage::freeItemValue(Item *item) {
    switch (item->mType) {
        case kTypeMessage:
        {
            if (item->aMessage != nullptr) {
                item->aMessage = nullptr;
            }
            break;
        }

        default:
            break;
    }
    item->mType = kTypeInt32; // clear type
}

inline size_t AMessage::findItemIndex(const std::string &name) const {
    size_t i = 0;
    for (; i < mItems.size(); i++) {
        if( mItems[i].mName == name ){
            break;
        }
    }
    return i;
}

// assumes item's name was uninitialized or nullptr
void AMessage::Item::setName(const std::string &name) {
    mName = name;
}

AMessage::Item::Item(std::string name)
        : mType(kTypeInt32), mName(std::move(name)) {
}

AMessage::Item *AMessage::allocateItem(const std::string &name) {
    size_t i = findItemIndex(name);
    Item *item;

    if (i < mItems.size()) {
        item = &mItems[i];
        freeItemValue(item);
    } else {
        assert(mItems.size() < kMaxNumItems);
        i = mItems.size();
        // place a 'blank' item at the end - this is of type kTypeInt32
        mItems.emplace_back(name);
        item = &mItems[i];
    }

    return item;
}

const AMessage::Item *AMessage::findItem(
        const std::string &name, Type type) const {
    size_t i = findItemIndex(name);
    if (i < mItems.size()) {
        const Item *item = &mItems[i];
        return item->mType == type ? item : nullptr;

    }
    return nullptr;
}

bool AMessage::findAsFloat(const std::string &name, float *value) const {
    size_t i = findItemIndex(name);
    if (i < mItems.size()) {
        const Item *item = &mItems[i];
        switch (item->mType) {
            case kTypeFloat:
                *value = item->floatValue;
                return true;
            case kTypeDouble:
                *value = (float)item->doubleValue;
                return true;
            case kTypeInt64:
                *value = (float)item->int64Value;
                return true;
            case kTypeInt32:
                *value = (float)item->int32Value;
                return true;
            case kTypeSize:
                *value = (float)item->sizeValue;
                return true;
            default:
                return false;
        }
    }
    return false;
}

bool AMessage::findAsInt64(const std::string &name, int64_t *value) const {
    size_t i = findItemIndex(name);
    if (i < mItems.size()) {
        const Item *item = &mItems[i];
        switch (item->mType) {
            case kTypeInt64:
                *value = item->int64Value;
                return true;
            case kTypeInt32:
                *value = item->int32Value;
                return true;
            default:
                return false;
        }
    }
    return false;
}

bool AMessage::contains(const std::string &name) const {
    size_t i = findItemIndex(name);
    return i < mItems.size();
}

#define BASIC_TYPE(NAME,FIELDNAME,TYPENAME)                             \
void AMessage::set##NAME(const std::string &name, TYPENAME value) {            \
    Item *item = allocateItem(name);                                    \
    if (item) {                                                         \
        item->mType = kType##NAME;                                      \
        item->FIELDNAME = value;                                      \
    }                                                                   \
}                                                                       \
                                                                        \
/* NOLINT added to avoid incorrect warning/fix from clang.tidy */       \
bool AMessage::find##NAME(const std::string &name, TYPENAME *value) const {  /* NOLINT */ \
    const Item *item = findItem(name, kType##NAME);                     \
    if (item) {                                                         \
        *value = item->FIELDNAME;                                     \
        return true;                                                    \
    }                                                                   \
    return false;                                                       \
}

BASIC_TYPE(Int32,int32Value,int32_t)
BASIC_TYPE(Int64,int64Value,int64_t)
BASIC_TYPE(Size,sizeValue,size_t)
BASIC_TYPE(Float,floatValue,float)
BASIC_TYPE(Double,doubleValue,double)
BASIC_TYPE(Pointer,ptrValue,void *)

#undef BASIC_TYPE

void AMessage::setString(
        const std::string &name, const char *s, ssize_t len) {
    Item *item = allocateItem(name);
    if (item) {
        item->mType = kTypeString;
        item->stringValue.append(s, len < 0 ? strlen(s) : len);
    }
}

void AMessage::setString(
        const std::string &name, const std::string &s) {
    setString(name, s.c_str(), s.size());
}

void AMessage::setMessage(const std::string &name, const std::shared_ptr<AMessage> &obj) {
    Item *item = allocateItem(name);
    if (item) {
        item->mType = kTypeMessage;
        item->aMessage = obj;
    }
}

void AMessage::setReplyToken(const std::string &name, const std::shared_ptr<AReplyToken> &obj){
    Item *item = allocateItem(name);
    if (item) {
        item->mType = KTypeReplyToken;
        item->aReplyToken = obj;
    }
}

void AMessage::setRect(
        const std::string &name,
        int32_t left, int32_t top, int32_t right, int32_t bottom) {
    Item *item = allocateItem(name);
    if (item) {
        item->mType = kTypeRect;

        item->rectValue.mLeft = left;
        item->rectValue.mTop = top;
        item->rectValue.mRight = right;
        item->rectValue.mBottom = bottom;
    }
}

bool AMessage::findString(const std::string &name, std::string &value) const {
    const Item *item = findItem(name, kTypeString);
    if (item) {
        value = item->stringValue;
        return true;
    }
    return false;
}

bool AMessage::findMessage(const std::string &name, std::shared_ptr<AMessage> &obj) const {
    const Item *item = findItem(name, kTypeMessage);
    if (item) {
        obj = item->aMessage;
        return true;
    }
    return false;
}

bool AMessage::findReplyToken(const std::string &name, std::shared_ptr<AReplyToken> &obj) const {
    const Item *item = findItem(name, KTypeReplyToken);
    if (item) {
        obj = item->aReplyToken;
        return true;
    }
    return false;
}


bool AMessage::findRect(
        const std::string &name,
        int32_t *left, int32_t *top, int32_t *right, int32_t *bottom) const {
    const Item *item = findItem(name, kTypeRect);
    if (item == nullptr) {
        return false;
    }

    *left = item->rectValue.mLeft;
    *top = item->rectValue.mTop;
    *right = item->rectValue.mRight;
    *bottom = item->rectValue.mBottom;

    return true;
}

void AMessage::deliver() {
    std::shared_ptr<AHandler> handler = mHandler.lock();
    if (handler == nullptr) {
        printf("failed to deliver message as target handler %ld is gone.", mTarget);
        return;
    }

    handler->deliverMessage(shared_from_this());
}

int32_t AMessage::post(int64_t delayUs) {
    std::shared_ptr<ALooper> looper = mLooper.lock();
    if (looper == nullptr) {
        printf("failed to post message as target looper for handler %ld is gone.", mTarget);
        return -ENOENT;
    }

    looper->post(shared_from_this(), delayUs);
    return NO_ERROR;
}

int32_t AMessage::postAndAwaitResponse(std::shared_ptr<AMessage> &response) {
    std::shared_ptr<ALooper> looper = mLooper.lock();
    if (looper == nullptr) {
        printf("failed to post message as target looper for handler %ld is gone.", mTarget);
        return -ENOENT;
    }

    std::shared_ptr<AReplyToken> token = looper->createReplyToken();
    if (token == nullptr) {
        printf("failed to create reply token");
        return -ENOMEM;
    }
    setReplyToken("replyID", token);

    looper->post(shared_from_this(), 0 /* delayUs */);
    return looper->awaitResponse(token, response);
}

int32_t AMessage::postReply(const std::shared_ptr<AReplyToken> &replyToken) {
    if (replyToken == nullptr) {
        printf("failed to post reply to a nullptr token");
        return -ENOENT;
    }
    std::shared_ptr<ALooper> looper = replyToken->getLooper();
    if (looper == nullptr) {
        printf("failed to post reply as target looper is gone.");
        return -ENOENT;
    }
    return looper->postReply(replyToken, shared_from_this());
}

bool AMessage::senderAwaitsResponse(std::shared_ptr<AReplyToken> &replyToken) {
    bool found = findReplyToken("replyID", replyToken);
    if (!found) {
        return false;
    }
    return true;
}

size_t AMessage::countEntries() const {
    return mItems.size();
}

const char *AMessage::getEntryNameAt(size_t index, Type *type) const {
    if (index >= mItems.size()) {
        *type = kTypeInt32;

        return nullptr;
    }

    *type = mItems[index].mType;

    return mItems[index].mName.c_str();
}

int32_t AMessage::setEntryNameAt(size_t index, const std::string &name) {
    if (index >= mItems.size()) {
        return BAD_INDEX;
    }
    if (name == mItems[index].mName) {
        return NO_ERROR; // name has not changed
    }
    if (findItemIndex(name) < mItems.size()) {
        return ALREADY_EXISTS;
    }
    mItems[index].mName.clear();
    mItems[index].setName(name);
    return NO_ERROR;
}

int32_t AMessage::removeEntryAt(size_t index) {
    if (index >= mItems.size()) {
        return BAD_INDEX;
    }
    // delete entry data and objects
    mItems[index].mName.clear();
    freeItemValue(&mItems[index]);

    // swap entry with last entry and clear last entry's data
    size_t lastIndex = mItems.size() - 1;
    if (index < lastIndex) {
        mItems[index] = mItems[lastIndex];
        mItems[lastIndex].mName.clear();
        mItems[lastIndex].mType = kTypeInt32;
    }
    mItems.pop_back();
    return NO_ERROR;
}

int32_t AMessage::removeEntryByName(const std::string &name) {
    size_t index = findEntryByName(name);
    if (index >= mItems.size()) {
        return BAD_INDEX;
    }
    return removeEntryAt(index);
}

size_t AMessage::findEntryByName(const std::string &name) const {
    return findItemIndex(name);
}

static bool isFourcc(uint32_t what) {
    return isprint(what & 0xff)
           && isprint((what >> 8) & 0xff)
           && isprint((what >> 16) & 0xff)
           && isprint((what >> 24) & 0xff);
}


std::string AMessage::debugString(int32_t indent) const {
    std::string s = "AMessage(what = ";

    char tmp[50];
    memset(tmp, 0, 50);

    if (isFourcc(mWhat)) {
        sprintf(tmp,
                "'%c%c%c%c'",
                (char)(mWhat >> 24),
                (char)((mWhat >> 16) & 0xff),
                (char)((mWhat >> 8) & 0xff),
                (char)(mWhat & 0xff));
    } else {
        sprintf(tmp,"0x%lx", mWhat);
    }
    s.append(tmp);

    if (mTarget != 0) {
        sprintf(tmp, ", target = %ld", mTarget);
        s.append(tmp);
    }
    s.append(") = {\n");

    for (size_t i = 0; i < mItems.size(); ++i) {
        const Item &item = mItems[i];

        switch (item.mType) {
            case kTypeInt32:
                sprintf(tmp,"int32_t %s = %ld", item.mName.c_str(), item.int32Value);
                break;
            case kTypeInt64:
                sprintf(tmp,"int64_t %s = %lld", item.mName.c_str(), item.int64Value);
                break;
            case kTypeSize:
                sprintf(tmp,"size_t %s = %d", item.mName.c_str(), item.sizeValue);
                break;
            case kTypeFloat:
                sprintf(tmp,"float %s = %f", item.mName.c_str(), item.floatValue);
                break;
            case kTypeDouble:
                sprintf(tmp,"double %s = %f", item.mName.c_str(), item.doubleValue);
                break;
            case kTypePointer:
                sprintf(tmp,"void *%s = %p", item.mName.c_str(), item.ptrValue);
                break;
            case kTypeString:
                sprintf(tmp,
                        "string %s = \"%s\"",
                        item.mName.c_str(),
                        item.stringValue.c_str());
                break;

            case kTypeMessage:
                sprintf(tmp,
                        "AMessage %s = %s",
                        item.mName.c_str(),
                        item.aMessage->debugString(indent + item.mName.size() + 14).c_str());
                break;

            default:
                break;
        }

        s.append("  ");
        s.append(tmp);
        s.append("\n");
    }

    s.append("}");
    return s;
}

